﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using BuzzX8.Xmpp.Properties;
using BuzzX8.Xmpp.StreamElements;
using BuzzX8.Xmpp.StreamElements.Builders;

namespace BuzzX8.Xmpp
{
    public class XmppStreamReader
    {
        public static readonly ICollection<StreamElementBuilder> DefaultBuilders;

        static XmppStreamReader()
        {
            DefaultBuilders = new List<StreamElementBuilder>();
            AddDefaultBuilders();
        }

        private static void AddDefaultBuilders()
        {
            AddDefaultBuilder<TlsElementBuilder>();
            AddDefaultBuilder<StreamHeaderBuilder>();
            AddDefaultBuilder<PresenceStanzaBuilder>();
            AddDefaultBuilder<MessageStanzaBuilder>();
            AddDefaultBuilder<StreamFeaturesListBuilder>();
        }

        private static void AddDefaultBuilder<T>() where T : StreamElementBuilder
        {
            DefaultBuilders.Add(Activator.CreateInstance<T>());
        }

        private ICollection<StreamElementBuilder> builders;
        private XmlReader xmlReader;

        public XmppStreamReader(Stream stream)
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ConformanceLevel = ConformanceLevel.Fragment;
            xmlReader = XmlReader.Create(stream, settings);
            InitializeBuilders();
        }

        public XmppStreamReader(XmlReader xmlReader)
        {
            this.xmlReader = xmlReader;
            InitializeBuilders();
        }

        private void InitializeBuilders()
        {
            builders = CreateBuilderCollection();
            InitializeBuilderCollection(builders);
        }

        private ICollection<StreamElementBuilder> CreateBuilderCollection()
        {
            return new List<StreamElementBuilder>();
        }

        private void InitializeBuilderCollection(ICollection<StreamElementBuilder> builders)
        {
            foreach (StreamElementBuilder builder in DefaultBuilders)
            {
                builders.Add(builder);
            }
        }

        public ICollection<StreamElementBuilder> Builders
        {
            get { return builders; }
        }

        public StreamElement Read()
        {
            if (xmlReader.Name == "stream:stream") xmlReader.Read();
            if (xmlReader.NodeType == XmlNodeType.None) xmlReader.Read();
            if (xmlReader.NodeType == XmlNodeType.XmlDeclaration) xmlReader.Read();
            if (xmlReader.NodeType != XmlNodeType.Element) throw new InvalidOperationException();

            StreamElementBuilder builder = GetBuilderForElement(xmlReader.Name);
            BuildElement(builder);

            return builder.Element;
        }

        private StreamElementBuilder GetBuilderForElement(string elementName)
        {
            try
            {
                return builders.First(builder => builder.CanBuildElement(elementName));
            }
            catch (InvalidOperationException)
            {
                throw new InvalidOperationException(string.Format(Resources.NoBuilderForElementExceptionMessage, elementName));
            }
        }
        
        private void BuildElement(StreamElementBuilder builder)
        {
            string elementName = xmlReader.Name;
            builder.StartNewElement(elementName);
            BuildAttributes(builder);
            if (xmlReader.Name == "stream:stream") return;
            
            while (xmlReader.Read())
            {
                if (IsEndOfElement(elementName)) break;
                if (IsStartOfNode()) builder.StartNode(xmlReader.Name);
                if (xmlReader.HasAttributes) BuildAttributes(builder);
                if (IsEndOfNode()) builder.EndNode();
                if (IsNodeValue()) builder.SetNodeValue(xmlReader.Value);
            }
        }

        private bool IsEndOfElement(string elementName)
        {
            return xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == elementName;
        }

        private bool IsStartOfNode()
        {
            return xmlReader.NodeType == XmlNodeType.Element;
        }

        private void BuildAttributes(StreamElementBuilder builder)
        {
            xmlReader.MoveToFirstAttribute();

            do builder.AddAttribute(xmlReader.Name, xmlReader.Value);
            while (xmlReader.MoveToNextAttribute());

            xmlReader.MoveToElement();
        }

        private bool IsEndOfNode()
        {
            return xmlReader.IsEmptyElement || xmlReader.NodeType == XmlNodeType.EndElement;
        }

        private bool IsNodeValue()
        {
            return xmlReader.NodeType == XmlNodeType.Text;
        }

        public T Read<T>()  where T : StreamElement
        {
            throw new NotImplementedException();
        }
    }
}
